/*
 *  NetTalk.c
 *  NetTalk
 *
 *  Created by Andrey on 22/09/2008.
 *  Copyright 2008 ИУ7-2003. All rights reserved.
 *
 */

// libevent support
#include <event.h>

// libc support
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

// Queues made "easy" 
#include <sys/queue.h>

// Write buffer size
#define NT_WRITE_BUFFER_SIZE	1024
#define NT_READ_BUFFER_SIZE		2048

#define	NETTALK_DEBUG

#include "NetTalk.h"
#include "NetTalkClientMap.h"
#include "../ClientDispatch/ClientDispatchLog.h"

NTCallbacksPtr _callbacks;

void OnReadData(NTNetworkStream stream, short event, void* arguments)
{	
	NTClientInfoPtr clientInfo = (NTClientInfoPtr)arguments;
	
	Byte receiveBuffer[NT_READ_BUFFER_SIZE];
	memset(receiveBuffer, 0, sizeof(receiveBuffer));
	
	int bytesReceived;
	bytesReceived = read(stream, receiveBuffer, sizeof(receiveBuffer));
	
	if (bytesReceived == 0)
	{
		if (_callbacks->clientDisconnectionCallback != NULL)
			_callbacks->clientDisconnectionCallback(clientInfo->peerInfo->peerAddress);
		
		NTCloseConnection(clientInfo->peerInfo->peerAddress);
		
		return;
	}
	else if (bytesReceived < 0)
	{
		return;
	}
	
	if (_callbacks->dataReceivedCallback != NULL)
		_callbacks->dataReceivedCallback(clientInfo->peerInfo->peerAddress, 
										 InitDataBufferWithDataAndLength(receiveBuffer, bytesReceived));
}

void OnWriteData(NTNetworkStream stream, short event, void* arguments)
{
	NTClientInfoPtr clientInfo = (NTClientInfoPtr)arguments;
#ifdef NETTALK_DEBUG
	Log("Purging write buffers for clientInfo at %p", clientInfo);
#endif
	DataBufferPtr writeBuffer = (DataBufferPtr)TAILQ_FIRST(&clientInfo->writeBufferQueue);
	if (writeBuffer == NULL)
		return;
	
	int bytesToWrite = writeBuffer->dataLength - writeBuffer->currentOffset;
	int bytesWritten = write(stream, writeBuffer->data + writeBuffer->currentOffset, bytesToWrite);
	if (bytesWritten == -1)
	{
		if (errno == EINTR || errno == EAGAIN)
		{
			// Write was interrupted, reschedule write event
			event_add(&clientInfo->readyToWriteEvent, NULL);
		}
		
		return;
	}
	else if ((writeBuffer->currentOffset + bytesWritten) < writeBuffer->dataLength)
	{
		// Not all data has been written, update buffer offset and reschedule event
		writeBuffer->currentOffset += bytesWritten;
		event_add(&clientInfo->readyToWriteEvent, NULL);
		return;
	}
	
	// Data was completely written
	TAILQ_REMOVE(&(clientInfo->writeBufferQueue), writeBuffer, bufferQueueEntries);
	
	free(writeBuffer->data);
	free(writeBuffer);
}

void OnAcceptConnection(NTNetworkStream stream, short event, void* arguments)
{
	NTClientInfoPtr clientInfo = calloc(1, sizeof(NTClientInfo));
	
	clientInfo->peerInfo = NTAcceptConnection(stream);	
	
	event_set(&clientInfo->dataReceivedEvent, clientInfo->peerInfo->peerStream, EV_READ | EV_PERSIST, OnReadData, clientInfo);
	event_add(&clientInfo->dataReceivedEvent, NULL);
	event_set(&clientInfo->readyToWriteEvent, clientInfo->peerInfo->peerStream, EV_WRITE, OnWriteData, clientInfo);
	
	TAILQ_INIT(&clientInfo->writeBufferQueue);
	
	AddClientToMap(clientInfo->peerInfo->peerAddress, clientInfo);
	
	if (_callbacks->clientConnectionCallback != NULL)
		_callbacks->clientConnectionCallback(clientInfo->peerInfo->peerAddress);
}

void NTInit(NTCallbacksPtr callbacks)
{
	_callbacks = callbacks;
	InitClientMap();
	event_init();
}

void NTAwaitConnection(int port)
{
	NTNetworkStream listeningStream = NTStartListeningOnPort(port);
	
	if (listeningStream == INVALID_STREAM)
		return;
	
	struct event acceptEvent;
	event_set(&acceptEvent, listeningStream, EV_READ | EV_PERSIST, OnAcceptConnection, NULL);
	event_add(&acceptEvent, NULL);
	
	event_dispatch();
}

void NTSendData(AsciiStringPtr clientAddress, DataBuffer dataToSend)
{
	/*
	NTWriteBufferPtr newWriteBuffer = calloc(1, sizeof(NTWriteBuffer));
	if (newWriteBuffer == NULL)
		return;
	
	newWriteBuffer->data = (BytePtr)malloc(dataLength);
	memcpy(newWriteBuffer, dataToSend, dataLength);
	newWriteBuffer->totalLength = dataLength;
	newWriteBuffer->currentOffset = 0;
	*/
	
	DataBuffer newWriteBuffer = CloneDataBuffer(dataToSend);
	
#ifdef NETTALK_DEBUG
	Log("NetTalk: new write buffer at %p containing %s", newWriteBuffer.data, newWriteBuffer.data);
#endif
	NTClientInfoPtr clientInfo = GetNTClientInfoByAddress(clientAddress);
#ifdef NETTALK_DEBUG
	Log("NetTalk: NTClientInfo for %s at %p", clientAddress, clientInfo);
#endif
	if (clientInfo != NULL)
	{
		TAILQ_INSERT_TAIL(&clientInfo->writeBufferQueue, &newWriteBuffer, bufferQueueEntries);
		Log("NetTalk: Queued new write buffer, adding libevent event");
		// Что-то у нас с вызовом событий на запись, пока callback вручную
		//event_add(&clientInfo->readyToWriteEvent, NULL);
		OnWriteData(clientInfo->peerInfo->peerStream, 0, (void *)clientInfo);
	}
#ifdef NETTALK_DEBUG
	else
		Log("NetTalk: OMFG!! clientInfo is NULL!");
#endif
}

void NTCloseConnection(AsciiStringPtr clientAddress)
{
	NTClientInfoPtr clientInfo = GetNTClientInfoByAddress(clientAddress);
	
	RemoveClientFromMap(clientInfo->peerInfo->peerAddress);
	
	close(clientInfo->peerInfo->peerStream);
	event_del(&clientInfo->dataReceivedEvent);
	free(clientInfo->peerInfo->peerAddress);
	free(clientInfo->peerInfo);
	free(clientInfo);
}

void NTShutdown()
{
	PurgeClientMap();
}